#include "QDivaAppController.h"
#include "Basic_Dynamic_lyvm.h"
#include "Collision_lyvm.h"
#include "DIVA_Manager.h"
#include "LyvmLayoutLibrary.h"
#include "LayoutLibrary.h"
#include "xVMRelationStruct.h"
#include "QLogger.h"
#include <QMessageBox>
#include "NormalizedCoordinate.h"
#include "DIVA_VM.h"  
#include "KeyboardAction.h"
#include "DIVA_RenderData.h"
#include "ExplorBotDialog.h"  //for explorbot
#include "ExplorBotLibrary.h"

// Keep an eye on the instance numbers of this object
int QDivaAppController::instanceCount = 0;

//////////////////////////////////////////////////////////////////////////
// Initialization, constructor, and destructor
void QDivaAppController::init(Ogre::SceneManager *mgr, Ogre::Camera *camera){
	this->_camera = camera;
	this->_sceneMgr = mgr;
	this->_query = _sceneMgr->createRayQuery( Ogre::Ray() );
}

// Initialize all member to prevent troubles like NullPointerException.
QDivaAppController::QDivaAppController(){ 
	// Control the number of instances
	assert(instanceCount<=1);

	this->_currentSelectedObj = NULL;
	this->_currentMovableObj = NULL;
	this->_currentVM = NULL;
	this->_mouseOldPos = QPoint(0, 0);
	this->_mouseNewPos = QPoint(0, 0);
	this->_distance = Ogre::Vector3::ZERO;
	this->_currentMouseBtn = Qt::NoButton;
	this->initKeyboardActions();
	this->_isZooming = false;
	this->_zoomLevel = 0;

	instanceCount++;
}

// delete what we created.
QDivaAppController::~QDivaAppController(){
	delete _query; 
}

// public methods, called by KeyboardAction objects
//////////////////////////////////////////////////////////////////////////
bool QDivaAppController::isLeftButtonPressed(){
	return ( _currentMouseBtn == Qt::LeftButton );
}

void QDivaAppController::dragVM( DRAGGING_MODE mode ){	
	if ( _currentSelectedObj && _currentMovableObj ){
		assert( _mouseNewPos != QPoint( 0, 0 ) );
		NormalizedCoordinate screenPos(_mouseNewPos);
		DIVA_Manager *divaMgr = DIVA_Manager::getInstance();		
		_currentVM = divaMgr->get_VM_from_MovableObject( _currentMovableObj );
		if ( _currentVM != NULL ){
			DIVA_VM *rootParent = xVMRelationStruct::getInstance()->VMRS_toCompositeRootVM( _currentVM );
			Ogre::Vector3 absWorldPos = computeAbsPos(mode);
			if ( mode != DRAG_TO_NEW_POS && _currentVM->isDraggedOn() == false ){
				QLogger::getInstance()->append("Trying to drag VM forward or backward, but it is not dragging on.");
			} else if ( mode == DRAG_TO_NEW_POS && _currentVM->get_visible() == false ){
				QLogger::getInstance()->append("Trying to drag VM to new position, but it is invisible.");
			} else{
				if ( _currentVM == rootParent ){				
					_currentVM->VMDragged( screenPos.x(), screenPos.y(), absWorldPos.x, absWorldPos.y, absWorldPos.z);				
				} else{
					rootParent->VMDragged( screenPos.x(), screenPos.y(), absWorldPos.x, absWorldPos.y, absWorldPos.z);
				} // decide it is the VM or it is the root parent that should be dragged
			} // avoid some situations that we should not drag the VM
		} // if we can get a VM from the movable object 	
	} else{
		QLogger::getInstance()->append(
			"Trying to drag VM but no object selected and movable, please make sure that some object has been selected.");
	} // if there's a object selected and movable
}


// Protected methods, only called internally
//////////////////////////////////////////////////////////////////////////
void QDivaAppController::moveCurrentSelectedObj(){
	DIVA_Manager *divaManager = DIVA_Manager::getInstance();
	divaManager->lock();
	dragVM(DRAG_TO_NEW_POS);
	doDynamicLayout();
	collisionHandling();
	divaManager->release();
}

void QDivaAppController::doDynamicLayout(){
	assert(_currentVM);
	LyvmLayoutLibrary *lib = LyvmLayoutLibrary::getInstance();
	QMap<int, std::string> dly_id_name_list( lib->getDynamicIDNameList() );
	QMapIterator<int, std::string> itr( dly_id_name_list );
	while ( itr.hasNext() ){
		itr.next();
		Basic_Dynamic_lyvm* lyvm;
		lyvm = LyvmLayoutLibrary::getInstance()->getDynamicLyVMbyID( itr.key() );
		if( lyvm->getFlag() ){
			lyvm->isAppliedToTargetVM( _currentVM );
		}
	} // iterate through the id-name list
}

void QDivaAppController::collisionHandling(){
	if( Collision_lyvm::getInstance()->lyvmIsTurnOn() ){
		assert(_currentVM);
		std::vector<DIVA_VM*> nonMovableVMs;
		nonMovableVMs.push_back( _currentVM );
		Collision_lyvm::getInstance()->collision_handling( nonMovableVMs, _currentVM );
		nonMovableVMs.clear();
	}
}



void QDivaAppController::queryForMovableObj( const QPointF &mousePos ){
	// Setup the ray scene query
	assert( _camera != NULL );
	assert( _query != NULL );
	
	NormalizedCoordinate normalizedPos(mousePos);
	
	Ogre::Ray mouseRay = _camera->getCameraToViewportRay( normalizedPos.x(), normalizedPos.y() );
	_query->setRay( mouseRay );
	_query->setSortByDistance(true);
	// Execute query
	Ogre::RaySceneQueryResult &result = _query->execute();
	Ogre::RaySceneQueryResult::iterator itr;
	_currentSelectedObj = NULL;
	_currentMovableObj = NULL;
	// Iterate through the query results
	for ( itr = result.begin(); itr != result.end(); itr++ ){
		// if current item is movable
		if ( itr->movable != NULL){
			// and if current object name is valid
			if (isValidObjName( itr->movable->getName() ) ){				
				_currentSelectedObj = itr->movable->getParentSceneNode();
				_currentMovableObj = itr->movable;
				// if we can get a VM from the current movable object
				DIVA_VM *currentVM = DIVA_Manager::getInstance()->get_VM_from_MovableObject( _currentMovableObj );
				if ( currentVM != NULL ){					
					if ( currentVM->get_visible() == false ){
						// if this vm is invisible, we continue the ray tracing line by doing nothing here
						_currentMovableObj = NULL;
						_currentSelectedObj = NULL;
					} else{
						calculateDistance();
						break;
					} 
				} else{
					calculateDistance();
					break;
				} 
			}
		} 
	} // end of iteration
}

void QDivaAppController::dispatchClickEvent( const QPointF &mousePos ){
	assert( _currentMovableObj );
	NormalizedCoordinate nomalizedPos(mousePos);
	DIVA_Manager *divaMgr = DIVA_Manager::getInstance();
//	divaMgr->lock();
	DIVA_VM *currentVM = divaMgr->get_VM_from_MovableObject( _currentMovableObj );
	if ( currentVM != NULL ){
		DIVA_VM *rootParent = xVMRelationStruct::getInstance()->VMRS_toCompositeRootVM( currentVM );
		if ( currentVM == rootParent ){
			currentVM->VMPicked( nomalizedPos.x(), nomalizedPos.y(), 0);
		} else{
			rootParent->VMPicked( nomalizedPos.x(), nomalizedPos.y(), 0);
		}
	}
//	divaMgr->release();
}



void QDivaAppController::rotateCamera(){
	QPoint mouseDiff = ( _mouseNewPos - _mouseOldPos );
	DIVA_RenderData *data = DIVA_RenderData::getSingletonPtr();
	float speed = data->mAvgFrameTime * DEFAULT_ROTATE_SPEED;
	data->mRotX += -( mouseDiff.x() * speed );
	data->mRotY += -( mouseDiff.y() * speed );				
	//data->mRotX += mouseDiff.x();
	//data->mRotY += mouseDiff.y();
	_mouseOldPos = _mouseNewPos;
}

Ogre::Vector3 QDivaAppController::computeAbsPos( DRAGGING_MODE mode ){
	Ogre::Vector3 finalPos = Ogre::Vector3::ZERO;
	NormalizedCoordinate screenPos(_mouseNewPos);
	Ogre::Ray mouseRay = _camera->getCameraToViewportRay( screenPos.x(), screenPos.y() );
	assert( _distance != Ogre::Vector3::ZERO );	
	Ogre::Real point;
	Ogre::Vector3 cameraDist = _camera->getRealPosition() - _currentVM->getWorldPosition();
	switch(mode){
		case DRAG_BACKWARD:			
			point = cameraDist.length() - DEFAULT_DRAG_MOVE_SPEED;
			break;
		case DRAG_FORWARD:
			point = cameraDist.length() + DEFAULT_DRAG_MOVE_SPEED;
			break;
		case DRAG_TO_NEW_POS:
			point = _distance.length();
			break;
		default:
			assert(false);
	}
	finalPos = mouseRay.getPoint(point);
	return finalPos;
}

bool QDivaAppController::isValidObjName( const Ogre::String &currentObjName ){
	return currentObjName.find( MOVABLE_TEXT ) == STR_NOT_FOUND &&
		currentObjName.find( SHOULD_NOT_CLICKED ) == STR_NOT_FOUND &&
		currentObjName.find( SIMPLE_RENDERABLE ) == STR_NOT_FOUND;
}

void QDivaAppController::calculateDistance(){
	assert( _camera );
	assert( _currentSelectedObj );
	Ogre::Vector3 cameraPos = _camera->getRealPosition();
	Ogre::Vector3 currentSelectedObjPos = _currentSelectedObj->_getDerivedPosition();
	_distance = currentSelectedObjPos - cameraPos;
}

void QDivaAppController::initKeyboardActions(){
	_keyboardActions[Qt::Key_Escape] = new CloseDiva;

	_keyboardActions[Qt::Key_W] = new MoveUp;
	_keyboardActions[Qt::Key_Up] = new MoveUp;
	_keyboardActions[Qt::Key_S] = new MoveDown;
	_keyboardActions[Qt::Key_Down] = new MoveDown;
	_keyboardActions[Qt::Key_A] = new MoveLeft;
	_keyboardActions[Qt::Key_Left] = new MoveLeft;
	_keyboardActions[Qt::Key_D] = new MoveRight;
	_keyboardActions[Qt::Key_Right] = new MoveRight;

	_keyboardActions[Qt::Key_R] = new MoveForward;
	_keyboardActions[Qt::Key_PageUp] = new MoveForward;
	_keyboardActions[Qt::Key_F] = new MoveBackward;
	_keyboardActions[Qt::Key_PageDown] = new MoveBackward;
	
	_keyboardActions[Qt::Key_Z] = new RollRight;
	_keyboardActions[Qt::Key_X] = new RollLeft;
	_keyboardActions[Qt::Key_O] = new MoveToOrigin;
}



// public methods called by DIVA_FrameListener
void QDivaAppController::stopWheelZooming(){
	//////////////////////////////////////////////////////////////////////////
	// Story by Luke:
	//  For keyboard events, there are onKeyPressed and onKeyReleased event
	//  handlers provided by QWidget; however, there's no such support on 
	//  mouse wheel events, there's only one wheelEvent() in the QWidget class.
	//  That is, we have to manually add this method "stopWheelZooming" to 
	//  simulate wheel rotating stopped event thanks to the design of 
	//  DIVA_RenderData and DIVA_FrameListener.
	//  Better solution is needed.
	//////////////////////////////////////////////////////////////////////////

	if(_isZooming){
		DIVA_RenderData::getSingletonPtr()->moveBck(false);
		DIVA_RenderData::getSingletonPtr()->moveFwd(false);
		_isZooming = false;
		_zoomLevel = 0;
	}
}

int QDivaAppController::zoomLevel(){
	return _zoomLevel;
}

void QDivaAppController::mouseLeftButtonPressed( int mouseX, int mouseY ){
	this->_currentMouseBtn = Qt::LeftButton;
	this->_mouseOldPos = QPoint(mouseX, mouseY);

	if ( _currentSelectedObj != NULL ){
		_currentSelectedObj->showBoundingBox( false );	
	} // turn off bounding box 

	queryForMovableObj( QPointF(mouseX, mouseY) );

	// Show the bounding box to highlight the selected object
	if ( _currentSelectedObj != NULL ){
		_currentSelectedObj->showBoundingBox(true);
	}

	// Since a VM may be belonged to another composite VM, so we need to dispatch the clicked event to its root.
	if ( _currentMovableObj != NULL ){
		dispatchClickEvent( QPointF(mouseX, mouseY) );
	}
}

void QDivaAppController::mouseLeftButtonReleased( int mouseX, int mouseY ){
	this->_currentMouseBtn = Qt::NoButton;
}

void QDivaAppController::mouseRightButtonPressed( int mouseX, int mouseY ){
	this->_currentMouseBtn = Qt::RightButton;
	
	//Add for new Explor Bot 2012/09/06

	this->_mouseOldPos = QPoint(mouseX, mouseY);

	if ( _currentSelectedObj != NULL ){
		_currentSelectedObj->showBoundingBox( false );	
	} // turn off bounding box 

	queryForMovableObj( QPointF(mouseX, mouseY) );

	// Show the bounding box to highlight the selected object
	if ( _currentSelectedObj != NULL ){
		_currentSelectedObj->showBoundingBox(true);
	}

	// Since a VM may be belonged to another composite VM, so we need to dispatch the clicked event to its root.
	if ( _currentMovableObj != NULL ){
		dispatchClickEvent( QPointF(mouseX, mouseY) );
	}

	//check selected VM
	DIVA_VM *rootParent = NULL;
	ExplorBotDialog* ex;

	if ( _currentSelectedObj ){
		//assert( _mouseNewPos != QPoint( 0, 0 ) );
		NormalizedCoordinate screenPos(_mouseNewPos);
		DIVA_Manager *divaMgr = DIVA_Manager::getInstance();		
		_currentVM = divaMgr->get_VM_from_MovableObject( _currentMovableObj );
		if ( _currentVM != NULL ){
			rootParent = xVMRelationStruct::getInstance()->VMRS_toCompositeRootVM( _currentVM );
		} // if we can get a VM from the movable object 	
	} else{
		QLogger::getInstance()->append(
			"Trying to drag VM but no object selected and movable, please make sure that some object has been selected.");
		_currentVM = NULL;
	} // if there's a object selected and movable
	
	emit rightClicked(rootParent);
	
	
	//DIVA_RenderData::getSingletonPtr()->origin(true);
}

void QDivaAppController::mouseRightButtonReleased( int mouseX, int mouseY ){
	this->_currentMouseBtn = Qt::NoButton;
}

void QDivaAppController::mouseMoving( int mouseX, int mouseY ){
	_mouseNewPos = QPoint(mouseX, mouseY);
	if(this->_currentMouseBtn == Qt::LeftButton){ 
		// if the left button is pressed
		if( this->_currentSelectedObj != NULL ){ // if there's a selected object
			// update its coordinate
			moveCurrentSelectedObj();
		} else{ // no object clicked means it is the background got clicked
			rotateCamera();
		} 
	}
}


void QDivaAppController::keyboardPressed( int key ){
	if ( _keyboardActions.find( key ) != _keyboardActions.end() ){
		_keyboardActions[ key ]->onKeyPressed();
	} else{
		QLogger::getInstance()->append("Unsupported key pressed");
	}
}

void QDivaAppController::keyboardReleased( int key ){
	if ( _keyboardActions.find( key ) != _keyboardActions.end() ){
		_keyboardActions[ key ]->onKeyReleased();
	} else{
		QLogger::getInstance()->append("Unsupported key released");
	}
}

void QDivaAppController::mouseWheelRotated( int delta ){
	_isZooming = true;
	_zoomLevel = delta;
	if( delta > 0 ){ // rotated forwards
		DIVA_RenderData::getSingletonPtr()->moveFwd(true);
	} else if ( delta < 0 ){ // rotated backwards
		DIVA_RenderData::getSingletonPtr()->moveBck(true);
	} else{
		QString msg;
		msg += "The wheel is nether rotated forwards nor rotated backwards.";
		QLogger::getInstance()->append( msg );
	}
}

void QDivaAppController::changeBackground( const QString& newBackground ){
	this->_sceneMgr->setSkyBox(true, SKYBOX_LOCATION + std::string( newBackground.toUtf8().data() ) );
}

DIVA_VM* QDivaAppController::getCorrectVM()
{
	return _currentVM;
}